Pythonã®Abstract Base Classes (ABCs)ã®åãè§£ãæŸã¡ãŸãããããããã³ã«ããŒã¹ã®æ§é çåä»ããšå ¬åŒãªã€ã³ã¿ãŒãã§ãŒã¹èšèšã®éèŠãªéããåŠã³ãŸãããã
Python Abstract Base Classes: ãããã³ã«å®è£ vs. ã€ã³ã¿ãŒãã§ãŒã¹èšèšã®ç¿åŸ
ãœãããŠã§ã¢éçºã®äžçã§ã¯ãå ç¢ã§ä¿å®æ§ãé«ããã¹ã±ãŒã©ãã«ãªã¢ããªã±ãŒã·ã§ã³ã®æ§ç¯ã究極ã®ç®æšã§ãããããžã§ã¯ããå°æ°ã®ã¹ã¯ãªããããåœéçãªããŒã ã«ãã£ãŠç®¡çãããè€éãªã·ã¹ãã ãžãšæé·ããã«ã€ããŠãæç¢ºãªæ§é ãšäºæž¬å¯èœãªå¥çŽã®å¿ èŠæ§ãæéèŠã«ãªããŸããç°ãªãã³ã³ããŒãã³ãããç°ãªãã¿ã€ã ãŸãŒã³ã®ç°ãªãéçºè ã«ãã£ãŠæžããããã®ã§ãã£ãŠããã·ãŒã ã¬ã¹ãã€ç¢ºå®ã«é£æºã§ããããã«ããã«ã¯ã©ãããã°ããã§ããããïŒãã®çãã¯æœè±¡åã®ååã«ãããŸãã
Pythonã¯ãã®åçãªæ§è³ªãããæœè±¡åã«é¢ããŠæåãªå²åŠãæã£ãŠããŸãããããã¯ã¿ã€ãã³ã°ãã§ãããªããžã§ã¯ããã¢ãã«ãããæ©ããã¢ãã«ããã鳎ããªãããããã¢ãã«ãšããŠæ±ããŸãããã®æè»æ§ã¯Pythonã®æå€§ã®åŒ·ã¿ã®äžã€ã§ãããè¿ éãªéçºãšã¯ãªãŒã³ã§èªã¿ãããã³ãŒããä¿é²ããŸããããããå€§èŠæš¡ãªã¢ããªã±ãŒã·ã§ã³ã§ã¯ãæé»ã®åæã®ã¿ã«é Œããšã埮åŠãªãã°ãä¿å®ã®å°é£ã«ã€ãªããå¯èœæ§ããããŸããäºæãããã¢ãã«ããé£ã¹ãªããªã£ããã©ããªãã§ããããïŒããã§ãPythonã®Abstract Base Classes (ABCs)ãç»å ŽããPythonã®åçãªç²Ÿç¥ãç ç²ã«ããããšãªããå ¬åŒãªå¥çŽãäœæããããã®åŒ·åãªã¡ã«ããºã ãæäŸããŸãã
ããããããã«éèŠã§ãã°ãã°èª€è§£ãããåºå¥ããããŸããPythonã®ABCsã¯äžèœã®ããŒã«ã§ã¯ãããŸããããããã¯ãœãããŠã§ã¢èšèšã®2ã€ã®ç°ãªã匷åãªå²åŠãã€ãŸãç¶æ¿ãèŠæ±ããæç€ºçã§å ¬åŒãªã€ã³ã¿ãŒãã§ãŒã¹ã®äœæãšãæ©èœæ§ããã§ãã¯ããæè»ãªãããã³ã«ã®å®çŸ©ã«åœ¹ç«ã¡ãŸãããããã®2ã€ã®ã¢ãããŒãâã€ã³ã¿ãŒãã§ãŒã¹èšèšãšãããã³ã«å®è£ âã®éããçè§£ããããšããPythonã«ããããªããžã§ã¯ãæåèšèšã®å¯èœæ§ãæå€§éã«åŒãåºããæè»ã§å®å šãªã³ãŒããæžãããã®éµãšãªããŸãããã®ã¬ã€ãã§ã¯ãäž¡æ¹ã®å²åŠãæ¢æ±ããã°ããŒãã«ãªãœãããŠã§ã¢ãããžã§ã¯ãã§åã¢ãããŒãã䜿çšããææã«ã€ããŠã®å®è·µçãªäŸãšæç¢ºãªã¬ã€ãã³ã¹ãæäŸããŸãã
ãã©ãŒãããã«é¢ããæ³šæïŒç¹å®ã®ãã©ãŒãããå¶çŽãéµå®ããããããã®èšäºã®ã³ãŒãäŸã¯ã倪åãšæäœã®ã¹ã¿ã€ã«ã䜿çšããæšæºã®ããã¹ãã¿ã°å ã«è¡šç€ºãããŸããæé«ã®å¯èªæ§ã®ããã«ããšãã£ã¿ã«ã³ããŒããŠè²Œãä»ããããšããå§ãããŸãã
åºç€ïŒAbstract Base Classesãšã¯æ£ç¢ºã«ã¯äœã§ããïŒ
2ã€ã®èšèšå²åŠã«æ·±ãå ¥ãåã«ããã£ãããšããåºç€ã確ç«ããŸããããAbstract Base Classãšã¯äœã§ããããïŒãã®æ žå¿ã«ãããŠãABCã¯ä»ã®ã¯ã©ã¹ã®ãã«ãŒããªã³ãã§ããããã¯ãæºæ ãããã¹ãŠã®ãµãã¯ã©ã¹ãå®è£ ããªããã°ãªããªãã¡ãœãããšããããã£ã®ã»ãããå®çŸ©ããŸããããã®ãã¡ããªãŒã®äžéšã§ãããšäž»åŒµããã¯ã©ã¹ã¯ããããã®ç¹å®ã®æ©èœãæã£ãŠããå¿ èŠãããããšèšãæ¹æ³ã§ãã
Pythonã®çµã¿èŸŒã¿`abc`ã¢ãžã¥ãŒã«ã¯ãABCsãäœæããããã®ããŒã«ãæäŸããŸãã2ã€ã®äž»èŠãªã³ã³ããŒãã³ãã¯æ¬¡ã®ãšããã§ãã
- `ABC`: ABCãäœæããããã®ã¡ã¿ã¯ã©ã¹ãšããŠäœ¿çšããããã«ããŒã¯ã©ã¹ãææ°ã®Python (3.4+) ã§ã¯ã`abc.ABC`ãç¶æ¿ããã ãã§æžã¿ãŸãã
- `@abstractmethod`: ã¡ãœãããæœè±¡ãšããŠããŒã¯ããããã«äœ¿çšããããã³ã¬ãŒã¿ãABCã®ä»»æã®ãµãã¯ã©ã¹ã¯ããããã®ã¡ãœãããå®è£ ããªããã°ãªããŸããã
ABCsã管çããåºæ¬çãªã«ãŒã«ã¯2ã€ãããŸãã
- å®è£ ãããŠããªãæœè±¡ã¡ãœãããæã€ABCã®ã€ã³ã¹ã¿ã³ã¹ãäœæããããšã¯ã§ããŸãããããã¯ãã³ãã¬ãŒãã§ããã宿åã§ã¯ãããŸããã
- ãã¹ãŠã®å ·äœçãªãµãã¯ã©ã¹ã¯ããã¹ãŠã®ç¶æ¿ãããæœè±¡ã¡ãœãããå®è£ ããªããã°ãªããŸãããããã«å€±æããå Žåããã®ã¯ã©ã¹ãæœè±¡ã¯ã©ã¹ã«ãªããã€ã³ã¹ã¿ã³ã¹ãäœæããããšã¯ã§ããŸããã
å€å žçãªäŸãã¡ãã£ã¢ãã¡ã€ã«ãåŠçããã·ã¹ãã ã§ãããèŠãŠã¿ãŸãããã
äŸïŒã·ã³ãã«ãªMediaFile ABC
ããŸããŸãªçš®é¡ã®ã¡ãã£ã¢ãåŠçããå¿ èŠãããã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããŠãããšæ³åããŠãã ããããã©ãŒãããã«é¢ä¿ãªãããã¹ãŠã®ã¡ãã£ã¢ãã¡ã€ã«ã¯åçå¯èœã§ãããããã€ãã®ã¡ã¿ããŒã¿ãæã£ãŠããå¿ èŠãããããšãç§ãã¡ã¯ç¥ã£ãŠããŸãããã®å¥çŽãABCã§å®çŸ©ã§ããŸãã
import abc
class MediaFile(abc.ABC):
def __init__(self, filepath: str):
self.filepath = filepath
print(f"Base init for {self.filepath}")
@abc.abstractmethod
def play(self) -> None:
"""Play the media file."""
raise NotImplementedError
@abc.abstractmethod
def get_metadata(self) -> dict:
"""Return a dictionary of media metadata."""
raise NotImplementedError
çŽæ¥`MediaFile`ã®ã€ã³ã¹ã¿ã³ã¹ãäœæããããšãããšãPythonã¯ããã黿¢ããŸãã
# ããã¯TypeErrorãçºçãããŸã
# media = MediaFile("path/to/somefile.txt")
# TypeError: Can't instantiate abstract class MediaFile with abstract methods get_metadata, play
ãã®ãã«ãŒããªã³ãã䜿çšããã«ã¯ã`play()`ãš`get_metadata()`ã®å®è£ ãæäŸããå ·äœçãªãµãã¯ã©ã¹ãäœæããªããã°ãªããŸããã
class AudioFile(MediaFile):
def play(self) -> None:
print(f"Playing audio from {self.filepath}...")
def get_metadata(self) -> dict:
return {"codec": "mp3", "duration_seconds": 180}
class VideoFile(MediaFile):
def play(self) -> None:
print(f"Playing video from {self.filepath}...")
def get_metadata(self) -> dict:
return {"codec": "h264", "resolution": "1920x1080"}
`MediaFile`ã«ãã£ãŠå®çŸ©ãããå¥çŽãæºãããŠããããã`AudioFile`ãš`VideoFile`ã®ã€ã³ã¹ã¿ã³ã¹ãäœæã§ããããã«ãªããŸããããããABCsã®åºæ¬çãªä»çµã¿ã§ããããããçã®åã¯ããã®ã¡ã«ããºã ã*ã©ã®ããã«*䜿çšãããããçãŸããŸãã
æåã®å²åŠïŒABCsãšããŠã®å ¬åŒã€ã³ã¿ãŒãã§ãŒã¹èšèšïŒåç®åä»ãïŒ
ABCsã䜿çšããæåã§æãäŒçµ±çãªæ¹æ³ã¯ãå ¬åŒãªã€ã³ã¿ãŒãã§ãŒã¹èšèšã®ããã§ãããã®ã¢ãããŒãã¯ãJavaãC++ãC#ãªã©ã®èšèªããã®éçºè ã«ããç¥ãããŠããæŠå¿µã§ããåç®åä»ãã«åºã¥ããŠããŸããåç®ã·ã¹ãã ã§ã¯ãåã®äºææ§ã¯ããã®ååãšæç€ºçãªå®£èšã«ãã£ãŠæ±ºå®ãããŸãããã®æèã§ã¯ãã¯ã©ã¹ã¯æç€ºçã«`MediaFile` ABCãç¶æ¿ããŠããå Žåã«ã®ã¿`MediaFile`ãšèŠãªãããŸãã
ãããã§ãã·ã§ãã«èªå®ã®ãããªãã®ã ãšèããŠãã ãããèªå®ãããžã§ã¯ããããŒãžã£ãŒã«ãªãããã«ããã ããæ¯ãèãã ãã§ã¯äžååã§ããåŠç¿ããç¹å®ã®è©Šéšã«åæ Œããããªãã®è³æ Œãæç€ºçã«è¿°ã¹ãå ¬åŒãªèšŒææžãåãåãå¿ èŠããããŸããããªãã®èªå®ã®ååãšç³»çµ±ãéèŠã§ãã
ãã®ã¢ãã«ã§ã¯ãABCã¯è²ããªãå¥çŽãšããŠæ©èœããŸãããããç¶æ¿ããããšã«ãããã¯ã©ã¹ã¯ãå¿ èŠãªæ©èœãæäŸããããšãã·ã¹ãã å šäœã«æ£åŒã«çŽæããŸãã
äŸïŒããŒã¿ãšã¯ã¹ããŒããã¬ãŒã ã¯ãŒã¯
ããŸããŸãªåœ¢åŒã«ããŒã¿ããšã¯ã¹ããŒãã§ãããã¬ãŒã ã¯ãŒã¯ãæ§ç¯ããŠãããšæ³åããŠãã ããããã¹ãŠã®ãšã¯ã¹ããŒããã©ã°ã€ã³ã峿 Œãªæ§é ã«åŸã£ãŠããããšã確èªããããšèããŠããŸãã`DataExporter`ã€ã³ã¿ãŒãã§ãŒã¹ãå®çŸ©ã§ããŸãã
import abc
from datetime import datetime
class DataExporter(abc.ABC):
"""A formal interface for data exporting classes."""
@abc.abstractmethod
def export(self, data: list[dict]) -> str:
"""Exports data and returns a status message."""
pass
def get_timestamp(self) -> str:
"""A concrete helper method shared by all subclasses."""
return datetime.utcnow().isoformat()
class CSVExporter(DataExporter):
def export(self, data: list[dict]) -> str:
filename = f"export_{self.get_timestamp()}.csv"
print(f"Exporting {len(data)} rows to {filename}")
# ... actual CSV writing logic ...
return f"Successfully exported to {filename}"
class JSONExporter(DataExporter):
def export(self, data: list[dict]) -> str:
filename = f"export_{self.get_timestamp()}.json"
print(f"Exporting {len(data)} records to {filename}")
# ... actual JSON writing logic ...
return f"Successfully exported to {filename}"
ããã§ã`CSVExporter`ãš`JSONExporter`ã¯ãæç€ºçãã€æ€èšŒå¯èœãª`DataExporter`ã§ããã¢ããªã±ãŒã·ã§ã³ã®ã³ã¢ããžãã¯ã¯ãã®å¥çŽãå®å šã«ä¿¡é Œã§ããŸãã
def run_export_process(exporter: DataExporter, data_to_export: list[dict]):
print("--- Starting export process ---")
if not isinstance(exporter, DataExporter):
raise TypeError("Exporter must be a valid DataExporter implementation.")
status = exporter.export(data_to_export)
print(f"Process finished with status: {status}")
# Usage
data = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
run_export_process(CSVExporter(), data)
run_export_process(JSONExporter(), data)
ABCã¯ããã¹ãŠã®ãµãã¯ã©ã¹ãšå ±ææ©èœãæäŸããå ·äœçãªã¡ãœããã`get_timestamp()`ãæäŸããŠããããšã«æ³šæããŠãã ãããããã¯ã€ã³ã¿ãŒãã§ãŒã¹ããŒã¹ã®èšèšã«ãããäžè¬çã§åŒ·åãªãã¿ãŒã³ã§ãã
å ¬åŒã€ã³ã¿ãŒãã§ãŒã¹ã¢ãããŒãã®é·æãšçæ
é·æïŒ
- ææ§ãããªããæç€ºçïŒå¥çŽã¯éåžžã«æç¢ºã§ããéçºè ã¯ç¶æ¿ç·`class CSVExporter(DataExporter):`ãèŠãŠãã¯ã©ã¹ã®åœ¹å²ãšæ©èœãããã«çè§£ã§ããŸãã
- ããŒã«ãã¬ã³ããªãŒïŒIDEããªã³ã¿ãŒãéçè§£æããŒã«ã¯å¥çŽãç°¡åã«æ€èšŒã§ããåªãããªãŒãã³ã³ããªãŒããšãšã©ãŒãã§ãã¯ãæäŸããŸãã
- å ±ææ©èœïŒABCã¯å ·äœçãªã¡ãœãããæäŸã§ããçã®åºåºã¯ã©ã¹ãšããŠæ©èœããã³ãŒãã®éè€ãæžãããŸãã
- 銎æã¿ãããïŒãã®ãã¿ãŒã³ã¯ãã»ãšãã©ã®ä»ã®ãªããžã§ã¯ãæåèšèªããã®éçºè ã«ãšã£ãŠããã«èªèã§ããŸãã
çæïŒ
- ã¿ã€ããªçµåïŒå ·äœçãªã¯ã©ã¹ã¯ABCã«çŽæ¥çµã³ä»ããããŸããABCãç§»åãŸãã¯å€æŽããå¿ èŠãããå Žåããã¹ãŠã®ãµãã¯ã©ã¹ã«åœ±é¿ããŸãã
- ç¡¬çŽæ§ïŒå³æ Œãªéå±€é¢ä¿ã匷å¶ããŸããè«ççã«ãšã¯ã¹ããŒããšããŠæ©èœã§ãããããã§ã«å¥ã®ãäžå¯æ¬ ãªåºåºã¯ã©ã¹ãç¶æ¿ããŠããã¯ã©ã¹ã¯ã©ããªããŸããïŒPythonã®å€éç¶æ¿ã¯ããã解決ã§ããŸãããããèªäœãè€éãïŒãã€ã€ã¢ã³ãåé¡ãªã©ïŒãããããå¯èœæ§ããããŸãã
- 䟵襲æ§ïŒãµãŒãããŒãã£ã³ãŒããé©åãããããã«äœ¿çšã§ããŸããã`export()`ã¡ãœãããæã€ã¯ã©ã¹ãæäŸããã©ã€ãã©ãªã䜿çšããŠããå Žåãããããµãã¯ã©ã¹åããã«`DataExporter`ã«ããããšã¯ã§ããŸããïŒããã¯å¯èœã§ãªãããæãŸãããªãå ŽåããããŸãïŒã
2çªç®ã®å²åŠïŒABCsãšããŠã®ãããã³ã«å®è£ ïŒæ§é çåä»ãïŒ
2çªç®ã®ããããPythonicããªå²åŠã¯ãããã¯ã¿ã€ãã³ã°ã«æ²¿ã£ãŠããŸãããã®ã¢ãããŒãã¯æ§é çåä»ãã䜿çšããŸããããã§ã¯ãäºææ§ã¯ååã系統ã§ã¯ãªããæ§é ãšæ¯ãèãã«ãã£ãŠæ±ºå®ãããŸãããªããžã§ã¯ããä»äºãããããã«å¿ èŠãªã¡ãœãããšå±æ§ãæã£ãŠãããªãã宣èšãããã¯ã©ã¹éå±€ã«é¢ä¿ãªãããã®ä»äºã«é©åãªåãšèŠãªãããŸãã
æ³³ãèœåãèããŠã¿ãŠãã ãããã¹ã€ããŒãšèŠãªãããããã«ãèšŒææžãæã£ãŠããå¿ èŠãããã¹ã€ããŒããã¡ããªãŒããªãŒã®äžéšã§ããå¿ èŠããããŸãããæººããã«æ°Žäžã§èªåèªèº«ãæšé²ã§ãããªããæ§é çã«ã¯ã¹ã€ããŒã§ãã人ãç¬ãã¢ãã«ã¯ãã¹ãŠã¹ã€ããŒã«ãªãããšãã§ããŸãã
ABCsã¯ãã®æŠå¿µãå ¬åŒåããããã«äœ¿çšã§ããŸããå ±éã®åºåºã¯ã©ã¹ãç¶æ¿ããããšã匷å¶ããã®ã§ã¯ãªããä»ã®ã¯ã©ã¹ããå¿ èŠãªãããã³ã«ãå®è£ ããŠããå Žåã«ä»®æ³ãµãã¯ã©ã¹ãšããŠèªèããABCãå®çŸ©ã§ããŸããããã¯ãç¹å¥ãªããžãã¯ã¡ãœããïŒ`__subclasshook__`ã«ãã£ãŠéæãããŸãã
`isinstance(obj, MyABC)`ãŸãã¯`issubclass(SomeClass, MyABC)`ãåŒã³åºããšãPythonã¯ãŸãæç€ºçãªç¶æ¿ããã§ãã¯ããŸããããã倱æããå Žåã`MyABC`ã`__subclasshook__`ã¡ãœãããæã£ãŠãããã©ããããã§ãã¯ããŸããæã£ãŠããå ŽåãPythonã¯ãããåŒã³åºãããããããã®ã¯ã©ã¹ãããªãã®ãµãã¯ã©ã¹ãšèŠãªããŸããïŒããšå°ããŸããããã«ãããABCã¯æ§é ã«åºã¥ããŠã¡ã³ããŒã·ããåºæºãå®çŸ©ã§ããŸãã
äŸïŒ`Serializable`ãããã³ã«
èŸæžã«ã·ãªã¢ã©ã€ãºã§ãããªããžã§ã¯ãã®ãããã³ã«ãå®çŸ©ããŸããããã·ã¹ãã å ã®ãã¹ãŠã®ã·ãªã¢ã©ã€ãºå¯èœãªãªããžã§ã¯ããå ±éã®åºåºã¯ã©ã¹ããç¶æ¿ããããã«åŒ·å¶ããããããŸããããããã¯ããŒã¿ããŒã¹ã¢ãã«ãããŒã¿è»¢éãªããžã§ã¯ãããŸãã¯åçŽãªã³ã³ãããããããŸããã
import abc
class Serializable(abc.ABC):
@abc.abstractmethod
def to_dict(self) -> dict:
pass
@classmethod
def __subclasshook__(cls, C):
if cls is Serializable:
# 'to_dict'ãCã®ã¡ãœãã解決é åºã«ããããã§ãã¯
if any("to_dict" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
ããã€ãã®ã¯ã©ã¹ãäœæããŸããããéèŠïŒãããã®ã¯ã©ã¹ã¯ã©ãã`Serializable`ãç¶æ¿ããŸããã
class User:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
def to_dict(self) -> dict:
return {"name": self.name, "email": self.email}
class Product:
def __init__(self, sku: str, price: float):
self.sku = sku
self.price = price
# ãã®ã¯ã©ã¹ã¯ãããã³ã«ã«æºæ ããŠããŸãã
class Configuration:
def __init__(self, setting: str):
self.setting = setting
ãããããããã³ã«ã§ãã§ãã¯ããŠã¿ãŸãããã
print(f"Is User serializable? {isinstance(User('Test', 't@t.com'), Serializable))}")
print(f"Is Product serializable? {isinstance(Product('T-1000', 99.99), Serializable))}")
print(f"Is Configuration serializable? {isinstance(Configuration('ON'), Serializable))}")
# åºåïŒ
# Is User serializable? True
# Is Product serializable? False <- ãªãïŒä¿®æ£ããŸãããã
# Is Configuration serializable? False
ãããè峿·±ããã°ã§ãïŒ`Product`ã¯ã©ã¹ã«ã¯`to_dict`ã¡ãœããããããŸããã远å ããŸãããã
class Product:
def __init__(self, sku: str, price: float):
self.sku = sku
self.price = price
def to_dict(self) -> dict: # ã¡ãœããã远å
return {"sku": self.sku, "price": self.price}
print(f"Is Product now serializable? {isinstance(Product('T-1000', 99.99), Serializable))}")
# åºåïŒ
# Is Product now serializable? True
`User`ãš`Product`ã¯å ±éã®èŠªã¯ã©ã¹ïŒ`object`以å€ïŒãå ±æããŠããŸããããäž¡æ¹ã®ã¯ã©ã¹ããããã³ã«ãæºãããŠããã®ã§ãã·ã¹ãã ã¯äž¡æ¹ã`Serializable`ãšããŠæ±ãããšãã§ããŸããããã¯ãçµåãè§£é€ããäžã§ä¿¡ããããªãã»ã©åŒ·åã§ãã
ãããã³ã«ã¢ãããŒãã®é·æãšçæ
é·æïŒ
- æå€§éã®æè»æ§ïŒéåžžã«ç·©ãçµåãä¿é²ããŸããã³ã³ããŒãã³ãã¯ãå®è£ 系統ã§ã¯ãªããæ¯ãèãã®ã¿ãæ°ã«ããŸãã
- é©å¿æ§ïŒæ¢åã®ã³ãŒããç¹ã«ãµãŒãããŒãã£ã©ã€ãã©ãªã®ã³ãŒãããå ã®ã³ãŒãã倿Žããã«ã·ã¹ãã ã®ã€ã³ã¿ãŒãã§ãŒã¹ã«é©åãããã®ã«æé©ã§ãã
- ã³ã³ããžã·ã§ã³ã®ä¿é²ïŒæ·±ãã峿 Œãªç¶æ¿ããªãŒãä»ããŠã§ã¯ãªããç¬ç«ããæ©èœããæ§ç¯ããããªããžã§ã¯ãã®èšèšã¹ã¿ã€ã«ã奚å±ããŸãã
çæïŒ
- æé»ã®å¥çŽïŒã¯ã©ã¹ãšãããå®è£ ãããããã³ã«ãšã®é¢ä¿ã¯ãã¯ã©ã¹å®çŸ©ããããã«æããã§ã¯ãããŸãããéçºè ã¯ã`User`ãªããžã§ã¯ãã`Serializable`ãšããŠæ±ãããŠããçç±ãçè§£ããããã«ãã³ãŒãããŒã¹ãæ€çŽ¢ããå¿ èŠããããããããŸããã
- å®è¡æãªãŒããŒãããïŒ`isinstance`ãã§ãã¯ã¯ã`__subclasshook__`ãåŒã³åºããã¯ã©ã¹ã®ã¡ãœããã«å¯Ÿãããã§ãã¯ãå®è¡ããå¿ èŠããããããé ããªãå¯èœæ§ããããŸãã
- è€éãã®å¯èœæ§ïŒ`__subclasshook__`å ã®ããžãã¯ã¯ããããã³ã«ãè€æ°ã®ã¡ãœãããåŒæ°ããŸãã¯æ»ãå€ãå«ãå Žåãéåžžã«è€éã«ãªãå¯èœæ§ããããŸãã
çŸä»£ã®çµ±åïŒ`typing.Protocol`ãšéçè§£æ
Pythonã®å€§èŠæš¡ã·ã¹ãã ã§ã®äœ¿çšãå¢ããã«ã€ããŠãããè¯ãéçè§£æãžã®æ¬²æ±ãé«ãŸããŸããã`__subclasshook__`ã¢ãããŒãã¯åŒ·åã§ãããçŽç²ã«å®è¡æã¡ã«ããºã ã§ããã³ãŒããå®è¡ããåã«ãæ§é çåä»ãã®å©ç¹ãåŸããããšãããã©ãã§ããããïŒ
ããã¯ãPEP 544ã§`typing.Protocol`ãå°å ¥ããããã£ããã§ããããã¯ãäž»ã«MypyãPyrightããŸãã¯PyCharmã®ã€ã³ã¹ãã¯ã¿ãŒã®ãããªéçåãã§ãã«ãŒã察象ãšãããããã³ã«ãå®çŸ©ããããã®æšæºåããããšã¬ã¬ã³ããªæ¹æ³ãæäŸããŸãã
`Protocol`ã¯ã©ã¹ã¯ã`__subclasshook__`ã®äŸãšåæ§ã«æ©èœããŸããããã€ã©ãŒãã¬ãŒãã¯ãããŸãããã¡ãœãããšãã®ã·ã°ããã£ãå®çŸ©ããã ãã§ããäºææ§ã®ããã¡ãœãããšã·ã°ããã£ãæã€ä»»æã®ã¯ã©ã¹ã¯ãéçåãã§ãã«ãŒã«ãã£ãŠæ§é çã«äºææ§ããããšèŠãªãããŸãã
äŸïŒ`Quacker`ãããã³ã«
ææ°ã®ããŒã«ã§å€å žçãªããã¯ã¿ã€ãã³ã°ã®äŸãããäžåºŠèŠãŠã¿ãŸãããã
from typing import Protocol
class Quacker(Protocol):
def quack(self, volume: int) -> str:
"""Produces a quacking sound."""
... # 泚æïŒãããã³ã«ã¡ãœããã®æ¬äœã¯äžèŠã§ã
class Duck:
def quack(self, volume: int) -> str:
return f"QUACK! (at volume {volume})"
class Dog:
def bark(self, volume: int) -> str:
return f"WOOF! (at volume {volume})"
def make_sound(animal: Quacker):
print(animal.quack(10))
make_sound(Duck()) # éçè§£æã¯ãã¹ããŸã
make_sound(Dog()) # éçè§£æã¯å€±æããŸãïŒ
ãã®ã³ãŒããMypyã®ãããªåãã§ãã«ãŒã§å®è¡ãããšã`make_sound(Dog())`è¡ã«ãšã©ãŒã衚瀺ãããŸãïŒãArgument 1 to "make_sound" has incompatible type "Dog"; expected "Quacker"ããåãã§ãã«ãŒã¯ã`Dog`ã`quack`ã¡ãœãããæ¬ ããŠãããã`Quacker`ãããã³ã«ãæºãããªãããšãçè§£ããŠããŸããããã«ãããã³ãŒããå®è¡ãããåã«ãšã©ãŒãæ€åºãããŸãã
`@runtime_checkable`ã䜿çšããå®è¡æãããã³ã«
ããã©ã«ãã§ã¯ã`typing.Protocol`ã¯éçè§£æå°çšã§ããå®è¡æ`isinstance`ãã§ãã¯ã§äœ¿çšããããšãããšããšã©ãŒãçºçããŸãã
# isinstance(Duck(), Quacker) # -> TypeError: Protocol 'Quacker' cannot be instantiated
ãã ãã`@runtime_checkable`ãã³ã¬ãŒã¿ã䜿çšããŠãéçè§£æãšå®è¡æã®åäœãšã®éã®ã®ã£ãããåããããšãã§ããŸããããã¯åºæ¬çã«ãPythonã«`__subclasshook__`ããžãã¯ãèªåçã«çæããããã«æç€ºããŸãã
from typing import Protocol, runtime_checkable
@runtime_checkable
class Quacker(Protocol):
def quack(self, volume: int) -> str: ...
class Duck:
def quack(self, volume: int) -> str: return "..."
print(f"Is Duck an instance of Quacker? {isinstance(Duck(), Quacker))}")
# åºåïŒ
# Is Duck an instance of Quacker? True
ããã«ãããäž¡æ¹ã®äžçã®å©ç¹ãåŸãããŸããéçè§£æã®ããã®ã¯ãªãŒã³ã§å®£èšçãªãããã³ã«å®çŸ©ãšãå¿ èŠã«å¿ããå®è¡ææ€èšŒã®ãªãã·ã§ã³ã§ãããã ãããããã³ã«ã«å¯Ÿããå®è¡æãã§ãã¯ã¯æšæºã®`isinstance`ã³ãŒã«ãããé ãããšã«æ³šæããŠãã ããããããã£ãŠããããã¯è³¢æã«äœ¿çšãããã¹ãã§ãã
å®çšçãªæææ±ºå®ïŒã°ããŒãã«éçºè åãã¬ã€ã
ã§ã¯ãã©ã¡ãã®ã¢ãããŒããéžæãã¹ãã§ããããïŒçãã¯å®å šã«ç¹å®ã®ãŠãŒã¹ã±ãŒã¹ã«äŸåããŸããåœéçãªãœãããŠã§ã¢ãããžã§ã¯ãã«ãããäžè¬çãªã·ããªãªã«åºã¥ããå®è·µçãªã¬ã€ãã以äžã«ç€ºããŸãã
ã·ããªãª1ïŒã°ããŒãã«SaaS補åã®ãã©ã°ã€ã³ã¢ãŒããã¯ãã£ã®æ§ç¯
äžçäžã®ãã¡ãŒã¹ãããŒãã£ããã³ãµãŒãããŒãã£éçºè ã«ãã£ãŠæ¡åŒµãããã·ã¹ãã ïŒäŸïŒEã³ããŒã¹ãã©ãããã©ãŒã ãCMSïŒãèšèšããŠããŸãããããã®ãã©ã°ã€ã³ã¯ãã³ã¢ã¢ããªã±ãŒã·ã§ã³ãšæ·±ãçµ±åããå¿ èŠããããŸãã
- æšå¥šïŒå ¬åŒã€ã³ã¿ãŒãã§ãŒã¹ïŒåç®`abc.ABC`ïŒã
- çç±ïŒæç¢ºããå®å®æ§ãæç€ºæ§ãæåªå äºé ã§ãããã©ã°ã€ã³éçºè ããããªãã®`BasePlugin` ABCãç¶æ¿ããããšã«ãã£ãŠãæèçã«ãªããã€ã³ããªããã°ãªããªããè²ããªãå¥çŽãå¿ èŠã§ããããã«ãããAPIã¯ææ§ã§ãªããªããŸãããŸããåºåºã¯ã©ã¹ã«ïŒãã®ã³ã°ãèšå®ãžã®ã¢ã¯ã»ã¹ãåœéåãªã©ã®ïŒäžå¯æ¬ ãªãã«ããŒã¡ãœãããæäŸã§ããéçºè ãšã³ã·ã¹ãã ã«ãšã£ãŠå€§ããªå©ç¹ãšãªããŸãã
ã·ããªãª2ïŒè€æ°ã®ç¡é¢ä¿ãªAPIããã®è²¡åããŒã¿ã®åŠç
ããªãã®ãã£ã³ããã¯ã¢ããªã±ãŒã·ã§ã³ã¯ãããŸããŸãªã°ããŒãã«æ±ºæžã²ãŒããŠã§ã€ïŒStripeãPayPalãAdyenããããŠããããã©ãã³ã¢ã¡ãªã«ã®Mercado Pagoã®ãããªå°åãããã€ããŒããã®ãã©ã³ã¶ã¯ã·ã§ã³ããŒã¿ãæ¶è²»ããå¿ èŠããããŸãããããã®SDKãè¿ããªããžã§ã¯ãã¯ãããªãã®ç®¡çå€ã§ãã
- æšå¥šïŒãããã³ã«ïŒ`typing.Protocol`ïŒã
- çç±ïŒãããã®ãµãŒãããŒãã£SDKã®ãœãŒã¹ã³ãŒãã倿ŽããŠã`Transaction`åºåºã¯ã©ã¹ãç¶æ¿ãããããšã¯ã§ããŸãããããããããããã®ãã©ã³ã¶ã¯ã·ã§ã³ãªããžã§ã¯ãã«ã¯ãååããããã«ç°ãªãå Žåã§ãã`get_id()`ã`get_amount()`ã`get_currency()`ã®ãããªã¡ãœãããããããšãç§ãã¡ã¯ç¥ã£ãŠããŸãããããã³ã«ãšã¢ããã¿ãŒãã¿ãŒã³ã䜿çšããŠãçµ±äžããããã¥ãŒãäœæã§ããŸãããããã³ã«ã«ãããå¿ èŠãªããŒã¿ã®*圢ç¶*ãå®çŸ©ã§ããé©å¿å¯èœãªéããã©ã®ããŒã¿ãœãŒã¹ã§ãæ©èœããåŠçããžãã¯ãèšè¿°ã§ããŸãã
ã·ããªãª3ïŒå€§èŠæš¡ãªã¢ããªã·ãã¯ãªã¬ã¬ã·ãŒã¢ããªã±ãŒã·ã§ã³ã®ãªãã¡ã¯ã¿ãªã³ã°
ã¬ã¬ã·ãŒã¢ããªã¹ãææ°ã®ãã€ã¯ããµãŒãã¹ã«åè§£ããã¿ã¹ã¯ããããŸããæ¢åã®ã³ãŒãããŒã¹ã¯äŸåé¢ä¿ã®çµ¡ã¿åã£ããŠã§ãã§ããããã¹ãŠãäžåºŠã«æžãæããããšãªããæç¢ºãªå¢çãå°å ¥ããå¿ èŠããããŸãã
- æšå¥šïŒæ··åã§ããããããã³ã«ã«å€§ããäŸåããŸãã
- çç±ïŒãããã³ã«ã¯ã段éçãªãªãã¡ã¯ã¿ãªã³ã°ã®ããã®åªããããŒã«ã§ãããŸã`typing.Protocol`ã䜿çšããŠæ°ãããµãŒãã¹éã®çæ³çãªã€ã³ã¿ãŒãã§ãŒã¹ãå®çŸ©ããããšããå§ããããŸããæ¬¡ã«ãã¬ã¬ã·ãŒã³ã¢ã³ãŒããããã«å€æŽããããšãªããã¢ããªã¹ã®äžéšã«ãããã®ãããã³ã«ã«æºæ ãããã¢ããã¿ãŒãèšè¿°ã§ããŸããããã«ãããã³ã³ããŒãã³ããæ®µéçã«çµåè§£é€ã§ããŸããã³ã³ããŒãã³ããå®å šã«çµåè§£é€ããããããã³ã«çµç±ã§ã®ã¿éä¿¡ããããã«ãªããšãç¬èªã®ãµãŒãã¹ã«æœåºããæºåãæŽããŸããå ¬åŒABCã¯ãæ°ããã¯ãªãŒã³ãªãµãŒãã¹å ã®ã³ã¢ã¢ãã«ãå®çŸ©ããããã«åŸã§äœ¿çšãããå¯èœæ§ããããŸãã
çµè«ïŒæœè±¡åãã³ãŒãã«ç¹ã蟌ã
Pythonã®Abstract Base Classesã¯ãèšèªã®å®çšçãªèšèšã®èšŒã§ãããããã¯ãåŸæ¥ã®ãªããžã§ã¯ãæåããã°ã©ãã³ã°ã®æ§é åãããèŠåŸãšãããã¯ã¿ã€ãã³ã°ã®åçãªæè»æ§ã®äž¡æ¹ãå°éãããæŽç·Žãããæœè±¡åããŒã«ããããæäŸããŸãã
æé»ã®åæããå ¬åŒãªå¥çŽãžã®æ ã¯ãã³ãŒãããŒã¹ã®æçã®å åã§ããABCsã®2ã€ã®å²åŠãçè§£ããããšã«ãããããã¯ãªãŒã³ã§ãããä¿å®æ§ãé«ããé«åºŠã«ã¹ã±ãŒã©ãã«ãªã¢ããªã±ãŒã·ã§ã³ã«ã€ãªãããæ å ±ã«åºã¥ããã¢ãŒããã¯ãã£äžã®æææ±ºå®ãè¡ãããšãã§ããŸãã
äž»ãªãã€ã³ãããŸãšãããšæ¬¡ã®ããã«ãªããŸãã
- å ¬åŒã€ã³ã¿ãŒãã§ãŒã¹èšèšïŒåç®åä»ãïŒïŒæç€ºçã§ææ§ãããªããçºèŠå¯èœãªå¥çŽãå¿ èŠãªå Žåã¯ãçŽæ¥ç¶æ¿ã䌎ã`abc.ABC`ã䜿çšããŸããããã¯ããã¬ãŒã ã¯ãŒã¯ããã©ã°ã€ã³ã·ã¹ãã ãããã³ã¯ã©ã¹éå±€ãå¶åŸ¡ããç¶æ³ã«æé©ã§ããããã¯å®£èšã«ããã¯ã©ã¹ãäœã§ããããšããããšã§ãã
- ãããã³ã«å®è£ ïŒæ§é çåä»ãïŒïŒæè»æ§ãçµåè§£é€ãæ¢åã³ãŒããé©å¿ãããèœåãå¿ èŠãªå Žåã¯ã`typing.Protocol`ã䜿çšããŸããããã¯ãå€éšã©ã€ãã©ãªãšã®é£æºãã¬ã¬ã·ãŒã·ã¹ãã ã® refactorããŸãã¯æ¯ãèãããªã¢ãŒãã£ãºã ã®èšèšã«æé©ã§ããããã¯ããã®æ§é ã«ããã¯ã©ã¹ãäœãã§ããããšããããšã§ãã
ã€ã³ã¿ãŒãã§ãŒã¹ãšãããã³ã«ã®éã®éžæã¯ãåãªãæè¡çãªè©³çްã§ã¯ãªãããœãããŠã§ã¢ãã©ã®ããã«é²åãããã圢äœãåºæ¬çãªèšèšäžã®æ±ºå®ã§ããäž¡æ¹ãç¿åŸããããšã«ããã匷åã§å¹ççãªã ãã§ãªãã倿Žã«åŒ·ãããšã¬ã¬ã³ããªPythonã³ãŒããæžãããšãã§ããããã«ãªããŸãã